﻿using System;
using System.DirectoryServices.AccountManagement;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using GianosNET.Common.Classes;
using GianosNET.Common.Interfaces;
using GianosNET.ServerApp.Properties;
using RemotedServicesCommon.Interfaces;

namespace GianosNET.ServerApp.Implementation
{
    [ServiceBehavior(
        InstanceContextMode = InstanceContextMode.PerSession,
        UseSynchronizationContext = true,
        AutomaticSessionShutdown = true
        )]
    public class RemotedServiceManager:IRemotedServiceManager
    {
        IPubSubContract ServiceCallback = null;

        private NLog.Logger logger;

        #region Eventi

        public event CommonHandlers.LogChangeEventHandler LogChangeEvent;
        public event CommonHandlers.ServiceStatusChangedEventHandler ServiceStatusChangeEvent;
        public event CommonHandlers.LoopingStatusChangedEventHandler LoopingStatusChangeEvent;
        public event CommonHandlers.IdleCountdownChangedEventHandler LoopingIdleCountdownChangeEvent;

        #endregion

        public RemotedServiceManager()
        {
            this.logger = Program.logger;

            #region Aggancia agli eventi del ServerInfo le chiamate all'istanza del manager creata dal client che si è collegato
            
            Program.info.OnLogChanged += info_OnLogChanged;
            Program.info.OnServiceStatusChanged += info_OnServiceStatusChanged;
            Program.info.OnLoopingStatusChanged +=info_OnLoopingStatusChanged;
            Program.info.OnIdleCountdownChanged += info_OnIdleCountdownChanged;
            Program.info.OnForceFullClientRefresh += info_OnForceFullClientRefresh;

            #endregion
        }

      
    

        #region Eventi agganciati a ServerInfo (chiamte server -> client)

        void info_OnIdleCountdownChanged(Guid svcId, int secBeforeNextExecution)
        {
            try
            {
                if (ServiceCallback != null)
                    ServiceCallback.LoopingIdleCountdown(svcId, secBeforeNextExecution);
            }
            catch (Exception ex)
            {
                logger.TraceException("Errore in info_OnIdleCountdownChanged", ex);
                UnsubscribeClient();
            }
            
        }
        void info_OnLoopingStatusChanged(Guid svcId, LoopingService.LoopingStateEnum status)
        {
            try
            {
                if (ServiceCallback != null)
                    ServiceCallback.LoopingStatusChanged(svcId, status);
            }
            catch (Exception ex)
            {
                logger.TraceException("Errore in info_OnLoopingStatusChanged", ex);
                UnsubscribeClient();
            }
            
        }
        void info_OnServiceStatusChanged(Guid svcId, RemotedClass.RemotedClassStatusEnum status)
        {
            try
            {
                if (ServiceCallback != null)
                    ServiceCallback.ServiceStatusChanged(svcId, status);
            }
            catch (Exception ex)
            {
                logger.TraceException("Errore in info_OnServiceStatusChanged", ex);
                UnsubscribeClient();
            }
            
        }
        void info_OnLogChanged(Guid logId, string msg)
        {
            Thread thr = new Thread(_LogChangeHandler) { Priority = ThreadPriority.BelowNormal, IsBackground = true };
            thr.Start(new _cLogUpdate(logId, msg));
        }

        void info_OnForceFullClientRefresh()
        {
            try
            {
                if (ServiceCallback != null)
                    ServiceCallback.ForceFullClientRefresh();
            }
            catch (Exception ex)
            {
                logger.TraceException("Errore in info_OnForceFullClientRefresh", ex);
                UnsubscribeClient();
            }
        }

        private void UnsubscribeClient()
        {
            logger.Trace(string.Format("Unsubscribe"));

            Program.info.OnLogChanged -= info_OnLogChanged;
            Program.info.OnServiceStatusChanged -= info_OnServiceStatusChanged;
            Program.info.OnLoopingStatusChanged -= info_OnLoopingStatusChanged;
            Program.info.OnIdleCountdownChanged -= info_OnIdleCountdownChanged;
            Program.info.OnForceFullClientRefresh -= info_OnForceFullClientRefresh;
        }



        #endregion

        #region Authentication

        public bool Authenticate(string userId, string password, out string token)
        {
            token = "";
            bool isValid = true;

            logger.Trace(string.Format("Tentativo di accesso: {0}", userId));

            if (Settings.Default.RequireAuthentication)
            {
                // crea il "principal context"
                PrincipalContext pc = new PrincipalContext(ContextType.Domain, Settings.Default.DomainContext);

                // valida le credenziali
                string username = !string.IsNullOrEmpty(Settings.Default.DomainPrefix)
                                      ? userId.ToLower().Replace(
                                          string.Format("{0}\\", Settings.Default.DomainPrefix), "")
                                      : userId.ToLower();
                isValid = pc.ValidateCredentials(username, password, ContextOptions.SimpleBind);
                if (isValid)
                {
                    string[] au = Settings.Default.AllowedUsers.Split('|');
                    isValid = au.Any(utente => utente.ToLower() == username.ToLower());
                }
            }

            if (isValid)
            {
                logger.Trace(string.Format("Accesso riuscito: {0}", userId));

                Guid g = Guid.NewGuid();
                token = g.ToString();

                ServiceCallback = OperationContext.Current.GetCallbackChannel<IPubSubContract>();
                Console.Write("#");
            }
            else
            {
                logger.Trace(string.Format("Accesso non riuscito: {0}", userId));

                Console.Write("!");

                UnsubscribeClient();
            }

            return isValid;
        }

        public void LogOff()
        {
            UnsubscribeClient();
            Console.Write("@");
        }

        #endregion

        #region Implementation of IRemotedServiceManager

        private void ExecuteServerShutdown()
        {
            bool doShutdown = false;
            while (!doShutdown)
            {
                Console.Write("z");
                Thread.Sleep(1000);

                doShutdown = true;

                // Attende lo stop di tutti i servizi per eseguire lo shutdown
                foreach (ServiceInfo info in Program.info.Services())
                    if (info.ServiceStatus != RemotedClass.RemotedClassStatusEnum.Stopped ||
                        info.LoopingStatus != LoopingService.LoopingStateEnum.Stopped)
                    {
                        doShutdown = false;
                        break;
                    }
            }

            Console.Write("QUIT");

            Environment.Exit(0);
        }

        public void RequestServerShutdown()
        {
            Console.Write("!!!");
            logger.Trace(string.Format("Richiesta di shutdown del servizio.."));

            foreach (ServiceInfo info in Program.info.Services())
                StopService(info.SvcId);

            Thread thread = new Thread(ExecuteServerShutdown);
            thread.Start();
        }

        #endregion

        #region Eventi propagati al client (fail-safe e su thread separati per evitare freeze del server)

        //------------------------------------------------------------------------------------------------------

        private class _cLogUpdate
        {
            public _cLogUpdate(Guid logId, string message)
            {
                LogId = logId;
                Message = message;
            }

            public Guid LogId { get; private set; }
            public string Message { get; private set; }
        }
        //public void LogChangeHandler(Guid logId, string msg)
        //{
        //    Thread thr = new Thread(_LogChangeHandler) { Priority = ThreadPriority.BelowNormal, IsBackground = true };
        //    thr.Start(new _cLogUpdate(logId, msg));
        //}
        private void _LogChangeHandler(object o)
        {
            try
            {
                if (!(o is _cLogUpdate)) return;

                if (ServiceCallback != null)
                    ServiceCallback.LogUpdate(
                        ((_cLogUpdate)o).LogId,
                        ((_cLogUpdate)o).Message);
            }
            catch (Exception ex)
            {
                // Fail-safe
                UnsubscribeClient();
            }
        }

        #endregion

        #region ServiceCommands

        public void StartService(Guid svcId)
        {
            Program.info.ServiceStart(svcId);
        }

        public void StopService(Guid svcId)
        {
            Program.info.ServiceStop(svcId);
        }

        public void StartLoopingSession(Guid svcId)
        {
            Program.info.StartLoopingSession(svcId);
        }

        public void SetLoopingPause(Guid svcId, int secs)
        {
            Program.info.SetLoopingPause(svcId, secs);
        }

        #endregion

        #region Information

        public ServiceInfo[] GetServiceList()
        {
            return Program.info.Services();
        }

        public string GetServerVersion()
        {
            return Program.info.ServerVersion;
        }

        public LogQueue GetLog(Guid g)
        {
            return Program.info.GetLog(g);
        }

        #endregion
    }
}